use crate::rbac_core::RBAC;

use anyhow::Result;
use axum::{
    async_trait,
    extract::{FromRequest, RequestParts},
    http::StatusCode,
    response::IntoResponse,
    routing::{delete, get, post},
    Extension, Router,
};
use cores::{
    internal_error,
    responses::CusResponse,
    validator::{ValidatedJson, ValidatedQuery},
};
use diesel_adapter::casbin::MgmtApi;
use serde::Deserialize;
use tracing::instrument;
use validator::Validate;

pub fn rest_server() -> Router {
    Router::new()
        .route("/add", post(add_rbac))
        .route("/delete", delete(delete_rbac))
        .route("/valid", get(valid_rbac))
}

#[async_trait]
impl<B> FromRequest<B> for RBAC
where
    B: Send,
{
    type Rejection = (StatusCode, String);

    async fn from_request(req: &mut RequestParts<B>) -> Result<Self, Self::Rejection> {
        let Extension(rbac) = Extension::<RBAC>::from_request(req)
            .await
            .map_err(internal_error)?;

        let enforcer = rbac.enforcer.clone();

        tracing::info!("RBAC url:{:#?}", req.uri());
        // println!("RBAC url:{:#?}", req.uri());
        // println!("{:#?}", request.uri().path());

        Ok(Self { enforcer })
    }
}

#[derive(Debug, Deserialize, Validate)]
struct ValidRBAC {
    #[validate(length(min = 1, message = "user is required"))]
    user: String,
    #[validate(length(min = 1, message = "url is required"))]
    url: String,
    #[validate(length(min = 1, message = "method is required"))]
    method: String,
}

impl ValidRBAC {
    fn to_vec(&self) -> Vec<String> {
        vec![
            self.user.to_owned(),
            self.url.to_owned(),
            self.method.to_owned(),
        ]
    }
}

async fn valid_rbac(
    ValidatedQuery(valid): ValidatedQuery<ValidRBAC>,
    Extension(rbac): Extension<RBAC>,
) -> impl IntoResponse {
    CusResponse::ok(rbac.handler().await.has_policy(valid.to_vec()))
}

#[instrument]
async fn add_rbac(
    ValidatedJson(valid): ValidatedJson<ValidRBAC>,
    Extension(rbac): Extension<RBAC>,
) -> impl IntoResponse {
    CusResponse::ok(
        match rbac.handler().await.add_policy(valid.to_vec()).await {
            Ok(value) => value,
            Err(rejection) => {
                tracing::error!("{:?}", rejection);
                false
            }
        },
    )
}

async fn delete_rbac(
    ValidatedQuery(valid): ValidatedQuery<ValidRBAC>,
    Extension(rbac): Extension<RBAC>,
) -> impl IntoResponse {
    CusResponse::ok(
        rbac.handler()
            .await
            .remove_policy(valid.to_vec())
            .await
            .unwrap(),
    )
}
